(*
	vidDrvrH9550(cmd,parameters) -- Driver for the Hitachi 9550 videodisc player. The cmd parameter specifies the
		function to be performed (p1 is the first parameter after cmd; p2 is the second, etc.):

					Command		Function
					---------		--------
					chapter			Return the chapter currently being displayed.
					control			Execute a series of control functions. Each addition parameter is a keyword to be
										executed. If the keyword doesn't make sense here, pass it on to configureSPort.
										The following keywords are understood by the video driver:
													Keyword						Function
													--------						--------
													init or reset					Reset the player, configure the serial port (the baud rate is set to
																						the highest available for the selected player).
													eject or reject				Eject the disc from the player.
													audioOff						Turn off both audio channels.
													audio1On						Turn on audio channel 1.
													audio2On						Turn on audio channel 2.
													stereoOn						Turn on both audio channels.
													pictureOn/pictureOff	Turn on/off the picture.
													framesOn/framesOff	Turn on/off the display of frame numbers.
													frameMode					Set player to frame number mode.
													chapterMode					Set player to chapter number mode.
													timeMode						Set player to time mode.
													defaultComm					Set default communications settings for this player.
					extended		Execute a function specific to the player. This player doesn't have any (yet).
					fps				Set the frames per second for the next playVideo command to p1, which can be a number or
										one of "slowest", "slower", "slow", "normal", "fast", "faster", "fastest".
					frame			Return the current frame number.
					name			Return the long name of the player.
					play				Start a sequence playing, from p1 to p2, which are frame numbers, chapter numbers, or times,
										depending upon the mode.
					scan				Scan forward or backward, depending upong whether p1 is "forward" or "backward".
					search			Search to frame, chapter, or time p1.
					sendCmd		Send command p1 to the player and wait for an acknowlege.
					speeds			Return the frames per second speeds allowed with this player.
					status			Return the status of the play, which is a comma-separated list containing:
														Keyword											Meaning
														--------											--------
														doorOpen or park or still or play		State of player.
														CLV or CAV										Type of disc.
														disc12inch or disc8inch						Size of disc being played.
														side1 or side2									Side of disc being played.
					step				Step p1 frames forward (or backward if it's negative), and do it p2 times.
					time				Return the time of the frame currently being displayed, in 1/60ths of a second since the start
										of the disc.
					version			Return the version of this player driver.

	To compile and link this file using Macintosh Programmer's Workshop,

		pascal -w vidDrvrH9550.p
		link -m ENTRYPOINT -o HyperCommands -rt XFCN=8034 -sn Main=vidDrvrH9550 ¶
			vidDrvrH9550.p.o "{MPW}"Libraries:interface.o "{MPW}"PLibraries:PasLib.o

	Copyright © 1988 Apple Computer, Inc.

	2/88 - Initial coding by Harry R. Chesley.
*)

{$R-}

{$S vidDrvrH9550 } 	{ Segment name must be the same as the command name. }

unit DummyUnit;

interface

uses MemTypes, QuickDraw, OSIntf, ToolIntf, HyperXCmd;

procedure EntryPoint(paramPtr: XCmdPtr);
	
implementation

type

Str31 = String[31];

procedure vidDrvrH9550(paramPtr: XCmdPtr); forward;

procedure EntryPoint(paramPtr: XCmdPtr);

	begin
		vidDrvrH9550(paramPtr);
	end;

procedure vidDrvrH9550(paramPtr: XCmdPtr);

	type videoModes = (frameMode,chapterMode,timeMode);

	var returnValue: str255;
		pCount: integer;
		p1, p2: str255;
		str: str255;
		i: integer;
		oneCharString: String[1];
		theMode: videoModes;

	{$I XCmdGlue.inc}

	procedure Fail(errMsg: Str255); { set theResult and quit }
		begin
			paramPtr^.returnValue := PasToZero(errMsg);
			exit(vidDrvrH9550);
		end;

	{$I VideoUtil.inc}

	procedure sendCmd(theCommand, optParm: str255);
		{ Send a command to the player, waiting for the ack. Normally, the ack is
			the command sent with the top bit set. If optParm is present, that value
			is used instead. }

		var echoBack: str255;

		begin
			SendCardMessage(Concat('sendSPort "',theCommand,'"'));
			if optParm = '' then
				begin
					echoBack := Copy(theCommand,1,1);
					echoBack[1] := chr(ord(echoBack[1])+128);
				end
			else echoBack := optParm;
			EvalAndDispose(Concat('RecvUpTo("',echoBack,'",600,empty)'));
		end;

	function chapter: str255;
		{ Return the current chapter number. }

		var str: str255;
			endTick: longInt;
			charCount: longInt;

		begin
			{ Ask for the chapter number in binary. }
			sendCmd('l','l');
	
			{ Wait for the number or time out. }
			endTick := TickCount+120;
			repeat charCount := StrToLong(EvalStr('charsAvailable()'));
			until (charCount >= 1) or (TickCount > endTick);
	
			{ If we got the number... }
			if charCount >= 1 then
				begin
					{ str := character one. }
					str := EvalStr('recvChars(1)');
					if length(str) = 0 then
						begin
							str[0] := chr(1); str[1] := chr(0);
						end;
					{ Compute the chapter number. }
					chapter := LongToStr(BitAnd(ord(str[1]),255));
				end
			else chapter := 'noAnswer';
		end;

	procedure search(var toFrame: str255; blankSearch: boolean; mode: videoModes);
		{ Search to the specified frame, blanking if appropriate. }

		begin
			if blankSearch then sendCmd('~','');
			oneCharString[1] := chr(176);
			if mode = chapterMode then sendCmd(Concat('+,',toFrame,'A'),oneCharString)
			else sendCmd(Concat('+:',toFrame,'A'),oneCharString);
			if blankSearch then sendCmd('n','');
		end;

	procedure stop;
		{ Stop the player. }

		begin
			sendCmd('$','');
		end;

	procedure control(var keywd: str255);
		{ Handle a control command. }

		type
			audioModes = (off,oneOn,twoOn,stereo);

		var numberOfParms: integer;
			i: integer;
			parm: str255;

		procedure ejectPlayer;
			{ Eject the disc. }
	
			begin
				{ Send the appropriate eject command. }
				sendCmd('/','');
			end;
	
		procedure audio(onOff: audioModes);
			{ Set the audio mode. }
	
			begin
				{ Send the appropriate audio channel command. }
				case onOff of
					off:
						begin
							sendCmd('I','');
							sendCmd('K','');
						end;
					oneOn:
						begin
							sendCmd('H','');
							sendCmd('K','');
						end;
					twoOn:
						begin
							sendCmd('I','');
							sendCmd('J','');
						end;
					stereo:
						begin
							sendCmd('H','');
							sendCmd('J','');
						end;
					end;
			end;
	
		procedure picture(onOff: boolean);
			{ Turn the picture on or off. }
	
			begin
				{ Send the appropriate picture command. }
				if onOff then sendCmd('n','')
				else sendCmd('~','');
			end;

		procedure frames(onOff: boolean);
			{ Turn the frame number display on or off. }

			var str: str255;
				inChapterMode: boolean;

			begin
				{ Figure out if we're in chapter mode or not. }
				GetStrGlobal('videoMode',str);
				inChapterMode := StringEqual(str,'chapterMode');

				{ Send the appropriate frame display command. }
				if onOff then
					begin
						sendCmd('L','');
						if inChapterMode then sendCmd('N','');
					end
				else
					begin
						sendCmd('M','');
						sendCmd('O','');
					end;
			end;

		procedure defaultComm;
			{ Set the default communications settings. }

			begin
				{ Set the port configuration. }
				SendCardMessage('configureSPort baud9600,echoOff,editOff,linefeedOff,stripOff');
			end;

		procedure setMode(theMode: str255);
			{ Set the frame/chapter/time mode. }

			begin
				SetStrGlobal('videoMode',theMode)
			end;

		procedure initPlayer;
			{ Initialize the player. }

			var str: str255;

			begin
				{ Empty out the globals. }
				SetStrGlobal('videoMode','');
				SetStrGlobal('blankNextVideo','');
				SetStrGlobal('videoSpeed','');
	
				{ Send the reset command for this player. }
				sendCmd('h','');
				EvalAndDispose('recvUpTo(empty,0,empty)');
				SendCardMessage('sendSPort "%"');
				EvalAndDispose('recvUpTo(numToChar(charToNum("%")+128),1200,empty)');

				{ Force it to stop. }
				stop;

				{ Reset the player to known defaults. }
				setMode('');
				audio(stereo);
				frames(false);
				picture(true);
			end;
	
		begin
			if StringEqual(keywd,'init') or StringEqual(keywd,'reset') then initPlayer
			else if StringEqual(keywd,'eject') or StringEqual(keywd,'reject') then ejectPlayer
			else if StringEqual(keywd,'audioOff') then audio(off)
			else if StringEqual(keywd,'audio1On') then audio(oneOn)
			else if StringEqual(keywd,'audio2On') then audio(twoOn)
			else if StringEqual(keywd,'stereoOn') then audio(stereo)
			else if StringEqual(keywd,'pictureOn') then picture(true)
			else if StringEqual(keywd,'pictureOff') then picture(false)
			else if StringEqual(keywd,'framesOn') then frames(true)
			else if StringEqual(keywd,'framesOff') then frames(false)
			else if StringEqual(keywd,'defaultComm') then defaultComm
			else if StringEqual(keywd,'frameMode') then setMode('')
			else if StringEqual(keywd,'chapterMode') then setMode('chapterMode')
			else if StringEqual(keywd,'timeMode') then setMode('timeMode')
			else SendCardMessage(Concat('configureSPort ',keywd));
		end;

	function extended(var keywd: str255): str255;
		{ Execute an extended command or function. }

		begin
			{ No extended command for this player. }
		end;

	procedure fps(var fpsKeywd: str255);
		{ Set the frames per second. }

		var speed: str255;

		begin
			speed := '';

			{ Default speed settings are fine, except: }
			if StringEqual(fpsKeyWd,'fast') then speed := '90'
			else if StringEqual(fpsKeyWd,'fastest') then speed := '90';

			{ Set it. }
			if speed <> '' then SetStrGlobal('videoSpeed',speed);
		end;

	function frame: str255;
		{ Return the current frame number. }

		var str, str2: str255;
			endTick: longInt;
			charCount: longInt;

		begin
			{ Ask for the frame number in binary. }
			sendCmd('k','k');
	
			{ Wait for the number or time out. }
			endTick := TickCount+120;
			repeat charCount := StrToLong(EvalStr('charsAvailable()'));
			until (charCount >= 2) or (TickCount > endTick);
	
			{ If we got the number... }
			if charCount >= 2 then
				begin
					{ str := character one. }
					str := EvalStr('recvChars(1)');
					if length(str) = 0 then
						begin
							str[0] := chr(1); str[1] := chr(0);
						end;
					{ str2 := character two. }
					str2 := EvalStr('recvChars(1)');
					if length(str2) = 0 then
						begin
							str2[0] := chr(1); str2[1] := chr(0);
						end;
					{ Compute the frame number. }
					frame := LongToStr(BitOr(BitShift(ord(str[1]),8),ord(str2[1])))
				end
			else frame := 'noAnswer';
		end;

	function name: str255;
		{ Return the long name of the player. }

		begin
			name := 'Hitachi 9550';
		end;

	procedure play(var firstFrame,lastFrame,speed: str255; blankSearch: boolean; mode: videoModes);
		{ Play the segment. }

		var lastFrameNum: longInt;
			startHere: boolean;
			playToLast: boolean;
			fpsNum: longInt;
			str: str255;
			rev: boolean;

		begin
			{ Figure out if we're playing to special (non-numeric markers). }
			startHere := StringEqual(firstFrame,'here');
			playToLast := StringEqual(lastFrame,'lastFrame');
			if playToLast then
				begin
					if mode = chapterMode then
						begin
							lastFrame := '99';
							lastFrameNum := 99;
						end
					else
						begin
							lastFrame := '54000';
							lastFrameNum := 54000;
						end;
				end
			else lastFrameNum := StrToLong(lastFrame);
			fpsNum := StrToLong(speed);

			{ Figure out whether we're playing forward or reverse. }
			rev := (not playToLast) and
						(((not startHere) and (lastFrameNum < StrToLong(firstFrame))) or (lastFrameNum = 0));

			{ Go to the first frame. }
			if not startHere then search(firstFrame,blankSearch,mode);
			{ Play it. }
			if fpsNum = 30 then
				begin
					if playToLast then sendCmd('%','')
					else if lastFrameNum = 0 then sendCmd('B','')
					else if mode = chapterMode then sendCmd(Concat('+,t$',LongToStr(lastFrameNum-1),'A'),'A')
					else sendCmd(Concat('+:t$',lastFrame,'A'),'A');
				end
			else if fpsNum > 30 then
				begin
					if playToLast then sendCmd('!','')
					else if lastFrameNum = 0 then sendCmd('&','')
					else if mode = chapterMode then sendCmd('!','')
					else sendCmd(Concat('+!:t$',lastFrame,'A'),'A')
				end
			else
				begin
					if rev then str := 'E'
					else str := 'D';
					if fpsNum = 15 then str := Concat(str,'2')
					else if fpsNum = 10 then str := Concat(str,'3')
					else if fpsNum = 3 then str := Concat(str,'10')
					else str := Concat(str,'30');
					if playToLast or (mode = chapterMode) or (lastFrameNum = 0) then sendCmd(Concat(str,'A'),'A')
					else sendCmd(Concat(str,'t$',lastFrame,'A'),'A');
				end;
		end;

	function scan(scanForward: boolean): str255;
		{ Scan forward (if scanForward is true) or backward. }

		begin
			if scanForward then
				begin
					EvalAndDispose('recvUpTo(empty,0,empty)');
					SendCardMessage('sendSPort quote');
					EvalAndDispose('recvUpTo(numToChar(charToNum(quote)+128),600,empty)');
				end
			else sendCmd('''','');
			scan := '';
		end;

	procedure step(goForward: boolean);
		{ Step one frame forward (if goForward is true) or backward. }

		begin
			if goForward then SendCmd('$','')
			else SendCmd(')','');
		end;

	function speeds: str255;
		{ Return the valid speed for this player. }

		begin
			speeds := '1,3,10,15,30,90';
		end;

	function status: str255;
		{ Return the current player status. }

		var str: str255;
			theResult: str255;
			endTick: longInt;
			charCount: longInt;

		begin
			{ Ask for the play status. }
			sendCmd('i','i');
	
			{ Wait for the number or time out. }
			endTick := TickCount+120;
			repeat charCount := StrToLong(EvalStr('charsAvailable()'));
			until (charCount >= 1) or (TickCount > endTick);
	
			theResult := 'noAnswer';

			{ If we got the status... }
			if charCount >= 1 then
				begin
					{ str := character one. }
					str := EvalStr('recvChars(1)');
					if length(str) = 0 then
						begin
							str[0] := chr(1); str[1] := chr(0);
						end
					else str[1] := chr(BitAnd(ord(str[1]),127));
					if str[1] in ['*','C','$',')','u'] then theResult := 'still'
					else if str[1] = '/' then
						begin
							status := 'park';
							exit(status);
						end
					else theResult := 'play';
					
					{ Ask for the disc info. }
					sendCmd('w','w');
			
					{ Wait for the number or time out. }
					endTick := TickCount+120;
					repeat charCount := StrToLong(EvalStr('charsAvailable()'));
					until (charCount >= 1) or (TickCount > endTick);
					
					{ If we got the status... }
					if charCount >= 1 then
						begin
							{ str := character one. }
							str := EvalStr('recvChars(1)');
							if length(str) = 0 then
								begin
									str[0] := chr(1); str[1] := chr(0);
								end;
							if BitAnd(ord(str[1]),8) <> 0 then theResult := Concat(theResult,',,disc8inch')
							else theResult := Concat(theResult,',,disc12inch');
							if BitAnd(ord(str[1]),4) <> 0 then theResult := Concat(theResult,',side2')
							else theResult := Concat(theResult,',side1');
						end;
				end
			else theResult := 'noAnswer';

			status := theResult;
		end;

	function time: str255;
		{ Return the time for the current frame. }

		begin
			time := 'notImplemented';
		end;

	function version: str255;
		{ Return the version of this player driver. }

		begin
			version := 'H9550 1.0';
		end;

	begin
		pCount := paramPtr^.paramCount;

		if pCount <= 0 then Fail('parameter count is not > 0');

		if pCount > 1 then GetStrParm(2,p1)
		else p1 := '';
		if pCount > 2 then GetStrParm(3,p2)
		else p2 := '';

		oneCharString := 'x';

		GetStrParm(1,str);

		returnValue := '';

		if StringEqual(str,'chapter') then returnValue := chapter
		else if StringEqual(str,'control') then
			begin
				for i := 2 to pCount do
					begin
						GetStrParm(i,str);
						control(str);
					end;
			end
		else if StringEqual(str,'extended') then returnValue := extended(p1)
		else if StringEqual(str,'fps') then fps(p1)
		else if StringEqual(str,'frame') then returnValue := frame
		else if StringEqual(str,'name') then returnValue := name
		else if StringEqual(str,'play') then
			begin
				GetStrGlobal('videoMode',str);
				if StringEqual(str,'chapterMode') then theMode := chapterMode
				else if StringEqual(str,'timeMode') then theMode := timeMode
				else theMode := frameMode;
				GetStrGlobal('blankNextVideo',str);
				if str = '' then
					begin
						GetStrGlobal('videoSpeed',str);
						play(p1,p2,str,false,theMode);
					end
				else
					begin
						GetStrGlobal('videoSpeed',str);
						play(p1,p2,str,false,theMode);
					end;
			end
		else if StringEqual(str,'scan') then
			begin
				if length(p1) < 1 then returnValue := scan(true)
				else returnValue := scan(not ((p1[1] = 'b') or (p1[1] = 'B')));
			end
		else if StringEqual(str,'search') then
			begin
				GetStrGlobal('videoMode',str);
				if StringEqual(str,'chapterMode') then theMode := chapterMode
				else if StringEqual(str,'timeMode') then theMode := timeMode
				else theMode := frameMode;
				GetStrGlobal('blankNextVideo',str);
				search(p1,str <> '',theMode);
			end
		else if StringEqual(str,'sendCmd') then sendCmd(p1,p2)
		else if StringEqual(str,'step') then step(p1 = '1')
		else if StringEqual(str,'speeds') then returnValue := speeds
		else if StringEqual(str,'status') then returnValue := status
		else if StringEqual(str,'stop') then stop
		else if StringEqual(str,'time') then returnValue := time
		else if StringEqual(str,'version') then returnValue := version;

		{ Return the result (if any). }
		paramPtr^.returnValue := PasToZero(returnValue)
	end;

end.
